package com.xqb.modules.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.xqb.common.utils.BusinessUtils;
import com.xqb.modules.domain.dto.RecommendParamsDto;
import com.xqb.modules.domain.dto.SearchParamsDto;
import com.xqb.modules.domain.pojo.es.EsCourse;
import com.xqb.modules.mapper.LesCourseContentMapper;
import com.xqb.modules.mapper.LesCourseMapper;
import com.xqb.modules.repository.EsCourseRepository;
import com.xqb.modules.service.EsCourseService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: JinChen Zhu
 * @CreateTime: 2024-10-28  16:06
 */
@Service
@Component
@Slf4j
public class EsCourseServiceImpl implements EsCourseService {
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Autowired
    private EsCourseService esCourseService;
    @Autowired
    private LesCourseMapper lesCourseMapper;
    @Autowired
    private LesCourseContentMapper lesCourseContentMapper;
    @Autowired
    private EsCourseRepository esCourseRepository;

    private static Map<String, Float> getWeightsMap() {
        Map<String, Float> fieldsWithWeights = new HashMap<>();
        // 名称权重
        fieldsWithWeights.put("name", 10f);
        // 课程详情权重
        fieldsWithWeights.put("courseDetail", 8f);
        // 课程介绍权重
        fieldsWithWeights.put("courseIntroduce", 7f);
        // 使用人群权重
        fieldsWithWeights.put("usePeople", 3f);
        // 公告权重
        fieldsWithWeights.put("announcement", 2f);
        // 评分标准权重
        fieldsWithWeights.put("gradingCriteria", 1f);
        return fieldsWithWeights;
    }

    @Override
    public int importAll() {
        // 从数据库中查询所有课程信息
        List<EsCourse> lesCourses = lesCourseMapper.selectAll(null);
        // 将具有相同课程ID但不同teacherName的课程信息合并，teacherName可以为空
        lesCourses = BusinessUtils.collectLesTeachers(lesCourses);
//        lesCourses.forEach(System.out::println);

        // 将处理后的课程信息批量保存到Elasticsearch中
        Iterable<EsCourse> esCoursesIterable = esCourseRepository.saveAll(lesCourses);

        // 获取迭代器，用于遍历保存后的课程信息
        Iterator<EsCourse> iterator = esCoursesIterable.iterator();
        int res = 0;

        // 遍历保存后的课程信息，计数
        while (iterator.hasNext()) {
            res++; // 计数器加一
            iterator.next(); // 迭代下一个元素
        }

        // 打印日志，记录导入的课程信息条数
        log.info("共导入{}条数据至es", res);

        // 返回导入的课程信息条数
        return res;
    }

    /**
     * 搜索课程信息
     *
     * @param spd 搜索参数封装对象，包含分页信息、过滤条件、搜索关键词等
     * @return 搜索结果的分页对象，包含搜索到的课程列表和分页信息
     */
    @Override
    public Page<EsCourse> search(SearchParamsDto spd) {
        // 创建分页对象
        Pageable pageable = PageRequest.of(spd.getPageNum(), spd.getPageSize());

        // 初始化 NativeSearchQueryBuilder 对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 设置分页
        nativeSearchQueryBuilder.withPageable(pageable);

        // 设置过滤条件
        if (spd.isFilterable()) {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            if (spd.getCourseType() != 0) {
                // 课程类型过滤
                boolQueryBuilder.must(QueryBuilders.termQuery("courseType", spd.getCourseType())); // 课程类型过滤
            }
            if (spd.getInsId() != 0) {
                // 机构ID过滤
                boolQueryBuilder.must(QueryBuilders.termQuery("insId", spd.getInsId())); // 机构ID过滤
            }
            if (spd.getTeacherName() != null && !spd.getTeacherName().isEmpty()) {
                // 教师名称过滤
                boolQueryBuilder.must(QueryBuilders.termQuery("teacherName", spd.getTeacherName())); // 教师名称过滤
            }
            if (spd.getThirdCateId() != 0) {
                // 第三类ID过滤
                boolQueryBuilder.must(QueryBuilders.termQuery("thirdCateId", spd.getThirdCateId())); // 第三类ID过滤
            }
            nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
        }

        // 构建搜索查询
        String keyword = spd.getKeyword();
        if (StrUtil.isEmpty(keyword)) {
            // 无关键词时查询所有
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); // 无关键词时查询所有
        } else {
            // 创建一个空的FilterFunctionBuilder列表，用于存储构建的查询条件
            List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
            // 获取权重映射表，其中包含了字段名和对应的权重值
            Map<String, Float> fieldsWithWeights = getWeightsMap();
            // 遍历权重映射表
            for (Map.Entry<String, Float> entry : fieldsWithWeights.entrySet()) {
                // 为每个字段创建一个FilterFunctionBuilder，并将其添加到列表中
                // 使用matchQuery进行字段匹配，并使用weightFactorFunction设置权重
                filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery(entry.getKey(), keyword), ScoreFunctionBuilders.weightFactorFunction(entry.getValue())));
            }
            // 创建一个FilterFunctionBuilder数组，用于存储列表中的元素
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
            // 将列表中的元素转换为数组
            filterFunctionBuilders.toArray(builders);
            // 构建FunctionScoreQueryBuilder，设置分数计算模式为SUM，并设置最小分数为2
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders).scoreMode(FunctionScoreQuery.ScoreMode.SUM).setMinScore(2);
            // 将构建的查询条件设置到nativeSearchQueryBuilder中
            nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
        }

        // 设置排序
        Integer sort = spd.getSort(); // 排序类型：0-相关度，1-时间，2-价格，3-销量
        SortOrder isAsc = spd.isAsc(); // 排序方式：0-降序，1-升序
        nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); // 默认相关度降序排序
        if (sort == 0) {
            // 相关度排序
            nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(isAsc)); // 相关度排序
        } else if (sort == 1) {
            // 时间排序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("publishTime").order(isAsc)); // 时间排序
        } else if (sort == 2) {
            // 价格排序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(isAsc)); // 价格排序
        } else if (sort == 3) {
            // 销量排序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("depId").order(isAsc)); // 销量排序
        }

        // 构建查询对象并执行搜索
        NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
        log.info("DSL: {}", Objects.requireNonNull(searchQuery.getQuery())); // 记录完整的DSL
        SearchHits<EsCourse> searchHits = elasticsearchRestTemplate.search(searchQuery, EsCourse.class);

        // 处理搜索结果
        if (searchHits.getTotalHits() <= 0) {
            return new PageImpl<>(ListUtil.empty(), pageable, 0);
        }
        List<EsCourse> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
        return new PageImpl<>(searchProductList, pageable, searchHits.getTotalHits());
    }

    @Override
    public Page<EsCourse> recommend(RecommendParamsDto rpd) {
        return null;
    }
}



